﻿namespace UsageTracker.Logger.Loggers
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading;

    /// <summary>
    /// Singleton class for transmitting usage data over ReST to the specified endpoint
    /// </summary>
    public class ReStLogger : IUsageLogger
    {
        private static ReStLogger instance;
        private Queue<string> loggerQueue = new Queue<string>();
        private static int queueFlushSize;
        private string loggerUri = String.Empty;
        private static int queueRetentionTime;

        private Timer flushTimer;

        /// <summary>
        /// Private constructor
        /// </summary>
        private ReStLogger()
        {
            this.flushTimer = new Timer(this.FlushTimerCallback, null, 5000, queueRetentionTime * 1000);
        }

        /// <summary>
        /// Property to return the singleton instance of the logger
        /// </summary>
        public static ReStLogger Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ReStLogger();
                    SetupLogger();
                }
                return instance;
            }
        }

        /// <summary>
        /// Configures the logging and queuing parameters for the logger, if specified in the host configuration file.
        /// </summary>
        private static void SetupLogger()
        {
            string flushSize = "100";
            if (ConfigurationManager.AppSettings.AllKeys.Contains("USAGETRACK_LOGGER_QUEUE_FLUSH_SIZE"))
            {
                flushSize = ConfigurationManager.AppSettings.Get("USAGETRACK_LOGGER_QUEUE_FLUSH_SIZE");
            }
            int.TryParse(flushSize, out queueFlushSize);

            string retentionSecs = "15";
            if (ConfigurationManager.AppSettings.AllKeys.Contains("USAGETRACK_LOGGER_QUEUE_RETENTION_SECS"))
            {
                retentionSecs = ConfigurationManager.AppSettings.Get("USAGETRACK_LOGGER_QUEUE_RETENTION_SECS");
            }
            int.TryParse(retentionSecs, out queueRetentionTime);
        }

        /// <summary>
        /// Calls the flush method on the data queue after a pre-determined time. Default = 15 secs.
        /// Queue flush time can be specified in the config parameter USAGETRACK_LOGGER_QUEUE_RETENTION_SECS
        /// </summary>
        /// <param name="state"></param>
        private void FlushTimerCallback(object state)
        {
            this.FlushData();
            this.flushTimer.Change(queueRetentionTime * 1000, Timeout.Infinite);
        }

        /// <summary>
        /// Enqueues the message to be logged into an internal queue and based on the retention policies, initiates flushing of the data to the end repository.
        /// </summary>
        /// <param name="logMessage">Message to be logged</param>
        /// <param name="isLogImmediate">Specifies that this log messages should be logged immediately. Useful in cases of process crash/stop logging</param>
        public void Log(string logMessage, bool isLogImmediate)
        {
            if (this.loggerUri.Equals(String.Empty))
            {
                this.loggerUri = ConfigurationManager.AppSettings.Get("USAGETRACK_REST_ENDPOINT");
            }

            lock (this.loggerQueue)
            {
                if (isLogImmediate)
                {
                    this.PostData(logMessage);
                }
                else
                {
                    this.loggerQueue.Enqueue(logMessage);
                    if (this.loggerQueue.Count > queueFlushSize)
                    {
                        this.FlushData();
                    }
                }
            }
        }

        /// <summary>
        /// Iterate through each message in the internal queue and flush to the end repository.
        /// </summary>
        private void FlushData()
        {
            while (this.loggerQueue.Count > 0)
            {
                this.PostData(this.loggerQueue.Dequeue());
            }
        }

        /// <summary>
        /// Initiates a ReST call to the target logger endpoint and posts usage data
        /// </summary>
        /// <param name="usageData">String log data formatted as JSON</param>
        private void PostData(string usageData)
        {
            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
            client.UploadStringCompleted += this.Client_PostDataCompleted;
            try
            {
                client.UploadStringAsync(new Uri(this.loggerUri), "POST", "=" + usageData);
            }
            catch (Exception exp)
            {
                //Deliberately left blank as an extension point
                //Write to the default debug log location that we were unable to post usage data
            }
        }

        /// <summary>
        /// Callback event handler called when the ReST invocation is completed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Client_PostDataCompleted(object sender, UploadStringCompletedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    //Kept for future use
                    string response = e.Result;
                }
            }
            catch (Exception exp)
            {
                //Deliberately left blank as an extension point
                //Write to the default debug log location that we were unable to post usage data
            }
        }
    }
}



